package com.demo.auth.service;

import com.demo.auth.constant.RedisKey;
import com.demo.auth.constant.ClientId;
import com.demo.auth.dao.domain.AuthCodeUser;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.approval.TokenStoreUserApprovalHandler;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestValidator;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.Duration;
import java.util.Map;

@Service
public class AuthorizationCodeService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private AuthenticationTokenService authenticationTokenService;
    @Resource
    private AuthorizationCodeServices authorizationCodeServices;
    private final ClientDetailsService clientDetailsService;
    private final OAuth2RequestFactory oAuth2RequestFactory;
    private final TokenStoreUserApprovalHandler userApprovalHandler = new TokenStoreUserApprovalHandler();
    private final OAuth2RequestValidator oauth2RequestValidator = new DefaultOAuth2RequestValidator();

    public AuthorizationCodeService(ClientDetailsService clientDetailsService) {
        this.clientDetailsService = clientDetailsService;
        this.oAuth2RequestFactory = new DefaultOAuth2RequestFactory(clientDetailsService);

        userApprovalHandler.setRequestFactory(oAuth2RequestFactory);
        userApprovalHandler.setTokenStore(new InMemoryTokenStore());
    }

    public String generateCode(String responseType, AuthCodeUser user) {
        Map<String, String> parameters = Map.of("response_type", responseType,
                "client_id", ClientId.AUTHORIZATION_CODE,
                "userName", user.getUserName(),
                "userId", user.getId().toString());

        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = authenticationTokenService.initUsernamePasswordAuthenticationToken(user);

        AuthorizationRequest authorizationRequest = oAuth2RequestFactory.createAuthorizationRequest(parameters);
        ClientDetails client = clientDetailsService.loadClientByClientId(authorizationRequest.getClientId());
        oauth2RequestValidator.validateScope(authorizationRequest, client);
        authorizationRequest = userApprovalHandler.checkForPreApproval(authorizationRequest, usernamePasswordAuthenticationToken);

        String code = generateCode(authorizationRequest, usernamePasswordAuthenticationToken);
        stringRedisTemplate.opsForValue().set(RedisKey.authCodeKey(user.getId().toString()), code, Duration.ofMinutes(5));
        return code;
    }

    private String generateCode(AuthorizationRequest authorizationRequest, Authentication authentication)
            throws AuthenticationException {
        OAuth2Request storedOAuth2Request = oAuth2RequestFactory.createOAuth2Request(authorizationRequest);

        OAuth2Authentication combinedAuth = new OAuth2Authentication(storedOAuth2Request, authentication);
        return authorizationCodeServices.createAuthorizationCode(combinedAuth);
    }
}
